package yaml

import (
	"context"
	"fmt"
	core "gitee.com/xfrm/middleware/xcore"
	"github.com/fsnotify/fsnotify"
	"github.com/opentracing/opentracing-go"
	"github.com/spf13/viper"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"gitee.com/xfrm/middleware/xconfig"
)

const (
	defaultCluster              = "default"
	defaultCacheDir             = "/tmp/sconfcenter"
	defaultCfgDir               = "/opt/conf"
	defaultNamespaceApplication = "application"
)

type yamlDriver struct{}

type yamlConfigCenter struct {
	servLoc           string
	cfgdir            string
	mu                sync.Mutex
	observers         []*xconfig.ConfigObserver
	recalledObservers map[*xconfig.ConfigObserver]interface{}
	configFiles       map[string]string
	fileDatas         map[string][]byte
	parsers           map[string]*viper.Viper
}

func init() {
	xconfig.Register(xconfig.ConfigTypeYaml, &yamlDriver{})
}

// New return yaml config center
func (driver *yamlDriver) New(ctx context.Context, cfgroot string, serviceName string, namespaceNames []string, options ...xconfig.Option) (xconfig.ConfigCenter, error) {
	span, ctx := opentracing.StartSpanFromContext(ctx, "yamlDriver.New")
	defer span.Finish()
	//fun := "yamlDriver.New-->"
	cfgDir := cfgroot
	if cfgDir == "" {
		cfgDir = core.GetEnvWithDefault("CFGDIR", defaultCfgDir)
	}
	center := &yamlConfigCenter{
		cfgdir:            fmt.Sprintf("%s/%s", cfgDir, serviceName),
		observers:         make([]*xconfig.ConfigObserver, 0),
		servLoc:           serviceName,
		configFiles:       make(map[string]string),
		fileDatas:         make(map[string][]byte),
		parsers:           make(map[string]*viper.Viper),
		recalledObservers: make(map[*xconfig.ConfigObserver]interface{}),
	}
	filepath.Walk(center.cfgdir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".yaml") {
			namespace := fileNameToNamespace(info.Name())
			center.configFiles[namespace] = path
			data, err := ioutil.ReadFile(path)
			if err != nil {
				return err
			}
			center.fileDatas[namespace] = data
			parser := viper.New()
			parser.SetConfigFile(path)
			parser.SetConfigType("yml")
			parser.ReadInConfig()
			center.parsers[namespace] = parser
		}
		return nil
	})
	for _, opt := range options {
		if opt == nil {
			continue
		}
		opt(center)
	}
	center.StartWatchUpdate()

	return center, nil
}

func (m *yamlConfigCenter) StartWatchUpdate() {
	go func() {
		m.watchAllFiles()
	}()
}
func fileNameToNamespace(filename string) string {
	namespace := strings.ReplaceAll(strings.ToLower(filename), ".yaml", "")
	return namespace
}
func (m *yamlConfigCenter) watchAllFiles() {
	for k, parser := range m.parsers {
		parser.OnConfigChange(func(in fsnotify.Event) {
			var chgType xconfig.ChangeType
			switch in.Op {
			case fsnotify.Create:
				chgType = xconfig.ADD
			case fsnotify.Write:
				chgType = xconfig.MODIFY
			case fsnotify.Remove:
				chgType = xconfig.DELETE
			default:
				return
			}
			newData, _ := ioutil.ReadFile(in.Name)
			changEvent := &xconfig.ChangeEvent{
				Source:    xconfig.Yaml,
				Namespace: k,
				Changes: map[string]*xconfig.Change{
					"_": &xconfig.Change{
						OldValue:   string(m.fileDatas[k]),
						NewValue:   string(newData),
						ChangeType: chgType,
					},
				},
			}
			if changEvent != nil {
				for _, ob := range m.observers {
					go func() {
						ob.HandleChangeEvent(changEvent)
					}()
				}
			}
			return
		})
		parser.WatchConfig()
	}

}

func (m *yamlConfigCenter) RegisterObserver(ctx context.Context, observer *xconfig.ConfigObserver) func() {
	// 注册时 启动监听
	observer.StartWatch(ctx)
	m.mu.Lock()
	defer m.mu.Unlock()
	m.observers = append(m.observers, observer)
	return func() {
		m.mu.Lock()
		defer m.mu.Unlock()
		m.recalledObservers[observer] = struct {
		}{}
	}
}

func (m *yamlConfigCenter) Stop(ctx context.Context) error {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.Stop")
	defer span.Finish()
	return nil
}

func (m *yamlConfigCenter) SubscribeNamespaces(ctx context.Context, namespaceNames []string) error {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.SubscribeNamespaces")
	defer span.Finish()
	return nil
}

func (m *yamlConfigCenter) GetString(ctx context.Context, key string) (string, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetString")
	defer span.Finish()
	return m.GetStringWithNamespace(ctx, defaultNamespaceApplication, key)
}

func (m *yamlConfigCenter) GetStringWithNamespace(ctx context.Context, namespace, key string) (string, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetStringWithNamespace")
	defer span.Finish()
	m.mu.Lock()
	defer m.mu.Unlock()
	if parser, ok := m.parsers[namespace]; ok {
		return parser.GetString(key), true
	}
	return "", false
}

func (m *yamlConfigCenter) GetBool(ctx context.Context, key string) (bool, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetBool")
	defer span.Finish()

	return m.GetBoolWithNamespace(ctx, defaultNamespaceApplication, key)
}

func (m *yamlConfigCenter) GetBoolWithNamespace(ctx context.Context, namespace, key string) (bool, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetBoolWithNamespace")
	defer span.Finish()

	m.mu.Lock()
	defer m.mu.Unlock()

	if parser, ok := m.parsers[namespace]; ok {
		return parser.GetBool(key), true
	}
	return false, false
}

func (m *yamlConfigCenter) GetInt(ctx context.Context, key string) (int, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetInt")
	defer span.Finish()

	return m.GetIntWithNamespace(ctx, defaultNamespaceApplication, key)
}

func (m *yamlConfigCenter) GetIntWithNamespace(ctx context.Context, namespace, key string) (int, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetIntWithNamespace")
	defer span.Finish()
	i, ok := m.GetInt64WithNamespace(ctx, namespace, key)
	return int(i), ok
}

func (m *yamlConfigCenter) GetFloat64(ctx context.Context, key string) (float64, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetFloat64")
	defer span.Finish()
	return m.GetFloat64WithNamespace(ctx, defaultNamespaceApplication, key)
}

func (m *yamlConfigCenter) GetFloat64WithNamespace(ctx context.Context, namespace, key string) (float64, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetFloat64WithNamespace")
	defer span.Finish()

	m.mu.Lock()
	defer m.mu.Unlock()
	if parser, ok := m.parsers[namespace]; ok {
		return parser.GetFloat64(key), true
	}
	return 0, false
}

func (m *yamlConfigCenter) GetInt64(ctx context.Context, key string) (int64, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetInt64")
	defer span.Finish()
	return m.GetInt64WithNamespace(ctx, defaultNamespaceApplication, key)
}

func (m *yamlConfigCenter) GetInt64WithNamespace(ctx context.Context, namespace, key string) (int64, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetInt64WithNamespace")
	defer span.Finish()
	m.mu.Lock()
	defer m.mu.Unlock()

	if parser, ok := m.parsers[namespace]; ok {
		return parser.GetInt64(key), true
	}
	return 0, false
}

func (m *yamlConfigCenter) GetInt32(ctx context.Context, key string) (int32, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetInt32")
	defer span.Finish()
	return m.GetInt32WithNamespace(ctx, defaultNamespaceApplication, key)
}

func (m *yamlConfigCenter) GetInt32WithNamespace(ctx context.Context, namespace, key string) (int32, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetInt32WithNamespace")
	defer span.Finish()
	i, ok := m.GetInt64WithNamespace(ctx, namespace, key)
	return int32(i), ok
}

func (m *yamlConfigCenter) GetAllKeys(ctx context.Context) []string {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetAllKeys")
	defer span.Finish()

	return m.GetAllKeysWithNamespace(ctx, defaultNamespaceApplication)
}

func (m *yamlConfigCenter) GetAllKeysWithNamespace(ctx context.Context, namespace string) []string {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.GetAllKeysWithNamespace")
	defer span.Finish()
	m.mu.Lock()
	defer m.mu.Unlock()
	var keys []string
	if parser, ok := m.parsers[namespace]; ok {
		return parser.AllKeys()
	}
	return keys
}

func (m *yamlConfigCenter) Unmarshal(ctx context.Context, v interface{}) error {
	return m.UnmarshalWithNamespace(ctx, defaultNamespaceApplication, v)
}

func (m *yamlConfigCenter) UnmarshalWithNamespace(ctx context.Context, namespace string, v interface{}) error {
	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.UnmarshalWithNamespace")
	defer span.Finish()
	m.mu.Lock()
	defer m.mu.Unlock()
	if parser, ok := m.parsers[namespace]; ok {
		return parser.Unmarshal(v)
	}
	return fmt.Errorf("no namespace: %s", namespace)
}

func (m *yamlConfigCenter) UnmarshalKey(ctx context.Context, key string, v interface{}) error {
	return m.UnmarshalKeyWithNamespace(ctx, defaultNamespaceApplication, key, v)
}

func (m *yamlConfigCenter) UnmarshalKeyWithNamespace(ctx context.Context, namespace string, key string, v interface{}) error {

	span, _ := opentracing.StartSpanFromContext(ctx, "yamlConfigCenter.UnmarshalWithNamespace")
	defer span.Finish()
	m.mu.Lock()
	defer m.mu.Unlock()
	if parser, ok := m.parsers[namespace]; ok {
		return parser.UnmarshalKey(key, v)
	}
	return fmt.Errorf("no namespace: %s", namespace)
}
